﻿using System;
using System.Collections;
using System.Collections.Generic;
using BasicFramework.Components;
using BasicFramework.Helper;
using System.Reflection;

namespace BasicFramework.Entities
{
    public class Entity : IEntity
    {

        #region Fields

        private List<IComponent> components;

        private string name;
        private Guid guid;
        private IEntity parent;
        private bool disposed;
        private bool readOnly;
        private EntityManager manager;

        #endregion

        #region Properties

        public string Name
        {
            get { return name; }
        }

        public EntityManager Manager
        {
            get { return manager; }
            set { this.manager = value; }
        }


        public Guid GUID
        {
            get { return guid; }
        }

        public IEntity Parent
        {
            get { return parent; }
            set
            {
                if (value != this && value != this.parent)
                {
                    parent = value;
                    this.OnParentChanged(value);
                }
            }
        }

        public bool Enabled
        {
            get
            {
                foreach (IComponent component in this.components)
                {
                    if (component.Enabled)
                    {
                        return true;
                    }
                }
                return false;
            }
            set
            {
                //TODO: Enabled implementieren
                if (value)
                {
                    // TODO: Alte enabled States zurücksetzen
                }
                else
                {
                    // TODO: Alte enabled States speichern
                }
            }
        }


        public IComponent this[Type key]
        {
            get { return components.Find(x => x.GetType() == key); }
        }
        #endregion

        #region Constructors

        public Entity(string name)
        {
            this.name = name;
            this.guid = new Guid();
            this.components = new List<IComponent>();
        }

        public Entity(string name, List<IComponent> components)
        {
            this.components = new List<IComponent>();

            if (components != null && components.Count > 0)
            {
                foreach (IComponent component in components)
                {
                    this.Add(component);
                }
            }

            this.name = name;
            guid = Guid.NewGuid();
        }

        public Entity(string name, params IComponent[] components)
        {
            this.components = new List<IComponent>();

            if (components != null && components.Length > 0)
            {
                foreach (IComponent component in components)
                {
                    this.Add(component);
                }
            }

            this.name = name;
            guid = Guid.NewGuid();
        }

        ~Entity()
        {
            Dispose(false);
        }


        #endregion

        #region Disposing

        protected virtual void Dispose(Boolean disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.
                    foreach (IComponent component in this.components)
                    {
                        component.Dispose();
                    }

                }

                // Release unmanaged resources. If disposing is false, 
                // only the following code is executed.

            }
            disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        #region Events

        public event EventHandler<EntityEventArgs> ParentChanged;
        public event EventHandler<ComponentEventArgs> ComponentAdded;
        public event EventHandler<ComponentEventArgs> ComponentRemoved;


        #endregion

        #region Methods

        public void Add(IComponent item, bool overwrite)
        {
            if (!this.IsReadOnly)
            {
                // Check if component of the same type already exists
                if (this.components.Exists(x => (x.GetType() == item.GetType())))
                {
                    if (overwrite)
                    {
                        this.Remove(item);
                    }
                    else
                    {
                        // Cancel add Method because Component already exists
                        return;
                    }
                }
                // Check if component is realy deleted
                if (!this.components.Exists(x => (x.GetType() == item.GetType())))
                {
                    item.Owner = this;
                    this.components.Add(item);

                    this.OnComponentAdded(item);

                    item.Initialize(true);
                }

            }
        }

        /// <summary>
        /// Returns the first occurence of the specified ComponentType
        /// </summary>
        public T Find<T>() where T : IComponent
        {
            Type type = typeof(T);
            return (T)components.Find(x => x.GetType() == type || x.GetType().BaseType == type || x.GetType().HasInterface(type));

        }

        /// <summary>
        /// Returns a list of all Components with the specified Type
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public List<T> FindAll<T>() where T : IComponent
        {
            List<T> matches = new List<T>();

            // to avoid garbage, we create an array we can 'for' over
            IComponent[] tmpComponents = new IComponent[components.Count];

            components.CopyTo(tmpComponents, 0);

            for (int i = 0; i < tmpComponents.Length; i++)
            {
                if (tmpComponents[i] is T)
                {
                    matches.Add((T)tmpComponents[i]);
                }
            }

            return matches;
        }


        public bool Contains<T>() where T : IComponent
        {
            return this.components.Exists(x => x.GetType() == typeof(T));
        }

        public bool Remove<T>() where T : IComponent
        {
            if (this.Contains<T>())
            {
                IComponent component = this[typeof(T)];
                // Dispose Component
                component.Dispose();
                // Delete Component from List
                this.components.Remove(component);
            }

            return false;
        }


        public void Start()
        {
            this.Enabled = true;
            foreach (IComponent component in components)
            {
                component.Start();
            }
        }

        /// <summary>
        /// Update Method, is called for every Frame
        /// </summary>
        public void Update(float frame)
        {
            foreach (IComponent component in components)
            {
                component.Update(frame);
            }
        }

        /// <summary>
        /// Draw Method, is called for every Frame
        /// </summary>
        public void Draw(float frame)
        {
            foreach (IComponent component in components)
            {
                component.Draw(frame);
            }
        }

        public void LoadResources(bool loadAllContent)
        {
            foreach (IComponent component in components)
            {
                component.LoadResources(loadAllContent);
            }
        }
        public void UnloadResources(bool loadAllContent)
        {
            foreach (IComponent component in components)
            {
                component.UnloadResources(loadAllContent);
            }
        }

        protected virtual void OnParentChanged(IEntity entity)
        {
            if (this.ParentChanged != null)
            {
                this.ParentChanged(this, new EntityEventArgs(entity));
            }
        }

        protected virtual void OnComponentAdded(IComponent component)
        {
            Logger.Info("Component " + component.GetType().Name + " added");
            if (this.ComponentAdded != null)
            {
                this.ComponentAdded(this, new ComponentEventArgs(component));
            }
        }

        protected virtual void OnComponentRemoved(IComponent component)
        {
            Logger.Info("Component " + component.GetType().Name + " removed");
            if (this.ComponentRemoved != null)
            {
                this.ComponentRemoved(this, new ComponentEventArgs(component));
            }
        }

        #endregion

        #region Implementation of ICollection<IComponent>

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>1</filterpriority>
        public IEnumerator<IComponent> GetEnumerator()
        {
            return this.components.GetEnumerator();
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
        public void Add(IComponent item)
        {
            this.Add(item, false);
        }



        /// <summary>
        /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. </exception>
        public void Clear()
        {
            foreach (IComponent component in this.components)
            {
                component.Dispose();
            }

            this.components.Clear();
        }

        /// <summary>
        /// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value.
        /// </summary>
        /// <returns>
        /// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false.
        /// </returns>
        /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
        public bool Contains(IComponent item)
        {
            return components.Exists(x => x.GetType() == item.GetType());
        }

        /// <summary>
        /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
        /// </summary>
        /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param><param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception><exception cref="T:System.ArgumentException"><paramref name="array"/> is multidimensional.-or-The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.-or-Type <paramref name="T"/> cannot be cast automatically to the type of the destination <paramref name="array"/>.</exception>
        public void CopyTo(IComponent[] array, int arrayIndex)
        {
            this.components.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <returns>
        /// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </returns>
        /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
        public bool Remove(IComponent item)
        {
            if (this.components.Contains(item))
            {
                item.Dispose();
                return this.components.Remove(item);
                // TODO: Carefull removal
            }
            return false;
        }

        /// <summary>
        /// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <returns>
        /// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </returns>
        public int Count
        {
            get { return this.components.Count; }
        }

        /// <summary>
        /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
        /// </summary>
        /// <returns>
        /// true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
        /// </returns>
        public bool IsReadOnly
        {
            get { return this.readOnly; }
            set
            {
                if (value != this.readOnly)
                {
                    this.readOnly = value;
                }

            }
        }


        #endregion

    }

}
